/*
* eXist Open Source Native XML Database
* Copyright (C) 2010-2011 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package org.exist.security.internal;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
//import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.EXistException;
import org.exist.security.AuthenticationException;
import org.exist.storage.BrokerPool;
/**
* @author <a href="mailto:shabanovd@gmail.com">Dmitriy Shabanov</a>
*
*/
public class EXistDBLoginModule implements javax.security.auth.spi.LoginModule {
// private final static Logger LOG = LogManager.getLogger(EXistLoginModule.class);
// initial state
private Subject subject;
private CallbackHandler callbackHandler;
// private Map<String, ?> sharedState;
// private Map<String, ?> options;
// configurable option
private boolean debug = false;
// the authentication status
private boolean succeeded = false;
private boolean commitSucceeded = false;
private org.exist.security.Subject userPrincipal = null;
/**
* Initialize this <code>LoginModule</code>.
*
* <p>
*
* @param subject
* the <code>Subject</code> to be authenticated.
* <p>
*
* @param callbackHandler
* a <code>CallbackHandler</code> for communicating with the end
* user (prompting for user names and passwords, for example).
* <p>
*
* @param sharedState
* shared <code>LoginModule</code> state.
* <p>
*
* @param options
* options specified in the login <code>Configuration</code> for
* this particular <code>LoginModule</code>.
*/
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map<String, ?> sharedState, Map<String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
// this.sharedState = sharedState;
// this.options = options;
// initialize any configured options
debug = "true".equalsIgnoreCase((String) options.get("debug"));
}
/**
* Authenticate the user by prompting for a user name and password.
*
* <p>
*
* @return true in all cases since this <code>LoginModule</code> should not
* be ignored.
*
* @exception FailedLoginException
* if the authentication fails.
* <p>
*
* @exception LoginException
* if this <code>LoginModule</code> is unable to perform the
* authentication.
*/
public boolean login() throws LoginException {
// prompt for a user name and password
if (callbackHandler == null)
{throw new LoginException("Error: no CallbackHandler available "
+ "to garner authentication information from the user");}
final Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("user name: ");
callbacks[1] = new PasswordCallback("password: ", false);
// username and password
String username;
char[] password;
try {
callbackHandler.handle(callbacks);
username = ((NameCallback) callbacks[0]).getName();
char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword();
if (tmpPassword == null) {
// treat a NULL password as an empty password
tmpPassword = new char[0];
}
password = new char[tmpPassword.length];
System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length);
((PasswordCallback) callbacks[1]).clearPassword();
} catch (final java.io.IOException ioe) {
throw new LoginException(ioe.toString());
} catch (final UnsupportedCallbackException uce) {
throw new LoginException("Error: " + uce.getCallback().toString()
+ " not available to garner authentication information"
+ " from the user");
}
// print debugging information
if (debug) {
System.out.println("\t\t[eXistLoginModule] user entered user name: " + username);
}
try {
userPrincipal = BrokerPool.getInstance().getSecurityManager().authenticate(username, password);
} catch (final AuthenticationException e) {
if (debug)
{System.out.println("\t\t[eXistLoginModule] authentication failed");}
throw new FailedLoginException(e.getMessage());
} catch (final EXistException e) {
throw new FailedLoginException(e.getMessage());
}
succeeded = userPrincipal.isAuthenticated();
return true;
}
/**
* <p>
* This method is called if the LoginContext's overall authentication
* succeeded (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
* LoginModules succeeded).
*
* <p>
* If this LoginModule's own authentication attempt succeeded (checked by
* retrieving the private state saved by the <code>login</code> method),
* then this method associates a <code>SamplePrincipal</code> with the
* <code>Subject</code> located in the <code>LoginModule</code>. If this
* LoginModule's own authentication attempted failed, then this method
* removes any state that was originally saved.
*
* <p>
*
* @exception LoginException
* if the commit fails.
*
* @return true if this LoginModule's own login and commit attempts
* succeeded, or false otherwise.
*/
public boolean commit() throws LoginException {
if (succeeded == false) {
return false;
} else {
// add a Principal (authenticated identity)
// to the Subject
if (!subject.getPrincipals().contains(userPrincipal))
{subject.getPrincipals().add(userPrincipal);}
if (debug) {
System.out.println("\t\t[eXistLoginModule] added User to Subject");
}
commitSucceeded = true;
return true;
}
}
/**
* <p>
* This method is called if the LoginContext's overall authentication
* failed. (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
* LoginModules did not succeed).
*
* <p>
* If this LoginModule's own authentication attempt succeeded (checked by
* retrieving the private state saved by the <code>login</code> and
* <code>commit</code> methods), then this method cleans up any state that
* was originally saved.
*
* <p>
*
* @exception LoginException
* if the abort fails.
*
* @return false if this LoginModule's own login and/or commit attempts
* failed, and true otherwise.
*/
public boolean abort() throws LoginException {
if (succeeded == false) {
return false;
} else if (succeeded == true && commitSucceeded == false) {
// login succeeded but overall authentication failed
succeeded = false;
userPrincipal = null;
} else {
// overall authentication succeeded and commit succeeded,
// but someone else's commit failed
logout();
}
return true;
}
/**
* Logout the user.
*
* <p>
* This method removes the <code>SamplePrincipal</code> that was added by
* the <code>commit</code> method.
*
* <p>
*
* @exception LoginException
* if the logout fails.
*
* @return true in all cases since this <code>LoginModule</code> should not
* be ignored.
*/
public boolean logout() throws LoginException {
subject.getPrincipals().remove(userPrincipal);
succeeded = commitSucceeded;
userPrincipal = null;
return true;
}
}